import {
    isObjectProperty,
    isBinaryExpression,
    BinaryExpression,
    Identifier,
    isIdentifier,
    ObjectExpression,
    isMemberExpression,
    isStringLiteral,
    ObjectMethod,
    isObjectMethod,
    isCallExpression,
    ExpressionStatement,
    isBooleanLiteral,
    isArrowFunctionExpression,
    isFunctionExpression,
    isNumericLiteral,
    isNullLiteral,
    ObjectProperty,
    isObjectExpression,
    isArrayExpression
} from '@babel/types';

import { ASTElement, ASTText } from 'vue-template-compiler';

import { IEvents, IProp } from '../../types/vue-sfc';
import { IParam, TValueOriginType } from '../../types/normal';
import { isNotEmptyObject, formatCodeForScript, formatCodeForObject, formatCodeForAnonymousFunction, formatCodeForArray, fixEndSemicolon } from '../../utils';

/**
 * 递归搜寻 Slot 节点
 */
export function findSlotNodes(node: ASTElement, result?: Array<ASTElement>) {
    const ret = result || [];

    if (node.tag === 'slot') {
        ret.push(node);
    } else if (node.children) {
        for (const child of node.children) {
            // @ts-ignore
            findSlotNodes(child, ret);
        }
    }

    return ret;
}

/**
 * 获取 Slot 说明
 */
export function getSlotNodeDesp(slotNode: ASTElement): string | undefined {
    let desp;
    const parentNode = slotNode.parent;

    if (parentNode?.children) {
        const childrens = parentNode.children;
        childrens.forEach((child, index) => {
            // @ts-ignore
            if (child?.tag === 'slot' && slotNode.slotName === child.slotName) {
                const commentNode = childrens[index - 1] as ASTText;
                if (commentNode && commentNode.isComment) {
                    desp = commentNode.text || '';
                    desp = desp.trim();
                }
            }
        });
    }

    return desp;
}

/**
 * 获取 Slot 名称
 */
export function getSlotName(slotNode: ASTElement) {
    return (slotNode.slotName || 'default').replace(/"/g, '');
}

/**
 * 是否 Event
 */
export function isEmitEvent(statement: ExpressionStatement) {
    const callName = getMethodCallName(statement);
    return callName === '$emit';
}

/**
 * 获取 Emit 事件信息
 */
export function getEmitEventInfo(statement: ExpressionStatement) {
    const info: { event?: string } = {};
    if (isCallExpression(statement.expression)) {
        statement.expression.arguments.forEach((arg, index) => {
            if (index === 0 && isStringLiteral(arg) && arg.value) {
                const name = arg.value;
                if (name) info.event = name;
            }
        });
    }
    return isNotEmptyObject(info) ? info : undefined;
}

/**
 * 整合 Emit 事件信息，因为数据来源不同
 */
export function combineEmitEventInfo(
    events: IEvents,
    data: {
        eventName: string;
        desp?: string;
        params?: Array<IParam>;
    }
) {
    // ...

    const splitArr = data.eventName.split(':');

    let name = data.eventName;
    let behavior = 'normal';
    if (splitArr.length > 1) {
        name = splitArr[1];
        behavior = splitArr[0];
    }

    if (events[name]) {
        if (!events[name].desp && data.desp) {
            events[name].desp = data.desp;
        }
        if (data.params && data.params.length) {
            events[name].params = data.params;
        }
    } else {
        // @ts-ignore
        events[name] = { behavior };
        if (data.desp) {
            events[name].desp = data.desp;
        }
        if (data.params && data.params.length) {
            events[name].params = data.params;
        }
    }
}

/**
 * 获取 Prop Name
 */
export function getObjectPropName(prop: ObjectExpression['properties'][0]) {
    let name = null;
    if (isObjectProperty(prop)) {
        if (isIdentifier(prop.key)) {
            name = prop.key.name;
        }
    }
    return name;
}

/**
 * 获取 Prop Value
 *
 * 如果属性值类型是箭头函数、普通函数、对象、数组，则直接格式化后按字符串输出
 */
export function getObjectPropValue(prop: ObjectExpression['properties'][0], code: string, options?: { formatCode?: boolean }) {
    const ret: {
        value: IProp['default'];
        type: TValueOriginType | 'binary';
    } = {
        value: null,
        type: 'null'
    };

    if (isObjectProperty(prop)) {
        if (isIdentifier(prop.value)) {
            ret.value = prop.value.name;
            ret.type = 'string';
            if (ret.value === 'undefined') {
                ret.value = undefined;
                ret.type = 'undefined';
            }
        } else if (isStringLiteral(prop.value)) {
            ret.value = prop.value.value;
            ret.type = 'string';
        } else if (isNumericLiteral(prop.value)) {
            ret.value = prop.value.value;
            ret.type = 'number';
        } else if (isBooleanLiteral(prop.value)) {
            ret.value = prop.value.value;
            ret.type = 'boolean';
        } else if (isNullLiteral(prop.value)) {
            ret.value = null;
            ret.type = 'null';
        } else if (isBinaryExpression(prop.value)) {
            ret.value = getBinaryExpressionValue(prop.value).join('|');
            ret.type = 'binary';
        } else if (isArrowFunctionExpression(prop.value)) {
            const { start, end } = prop.value;
            if (start !== null && end !== null) {
                ret.value = code.substring(start, end);
                ret.type = 'function';
                if (options?.formatCode) {
                    ret.value = formatCodeForScript(ret.value);
                    ret.value = fixEndSemicolon(ret.value);
                }
            }
        } else if (isFunctionExpression(prop.value)) {
            const { start, end } = prop.value;
            if (start !== null && end !== null) {
                ret.value = code.substring(start, end);
                ret.type = 'function';
                if (options?.formatCode) {
                    ret.value = formatCodeForAnonymousFunction(ret.value);
                    ret.value = fixEndSemicolon(ret.value);
                }
            }
        } else if (isObjectExpression(prop.value)) {
            const { start, end } = prop.value;
            if (start !== null && end !== null) {
                ret.value = code.substring(start, end);
                ret.type = 'object';
                if (options?.formatCode) {
                    ret.value = formatCodeForObject(ret.value);
                    ret.value = fixEndSemicolon(ret.value);
                }
            }
        } else if (isArrayExpression(prop.value)) {
            const { start, end } = prop.value;
            if (start !== null && end !== null) {
                ret.value = code.substring(start, end);
                ret.type = 'array';
                if (options?.formatCode) {
                    ret.value = formatCodeForArray(ret.value);
                    ret.value = fixEndSemicolon(ret.value, { isArray: true });
                }
            }
        } else {
            const { start, end } = prop.value;
            if (start !== null && end !== null) {
                ret.value = code.substring(start, end);
                ret.type = 'unknown';
            }
        }
    }

    if (isObjectMethod(prop)) {
        const { start, end } = prop.body;
        if (start !== null && end !== null) {
            ret.value = code.substring(start, end);
            ret.type = 'function';
            if (options?.formatCode) {
                ret.value = formatCodeForScript(ret.value);
                ret.value = fixEndSemicolon(ret.value);
            }
            ret.value = 'function () ' + ret.value;
        }
    }

    return ret;
}

/**
 * 获取节点对象属性值
 *
 * 如果属性值类型是箭头函数、普通函数、对象、数组，则直接格式化后按字符串输出
 */
export function getObjectPropertyValue(prop: ObjectProperty | ObjectMethod, code: string, options?: { formatCode?: boolean }) {
    let value;

    if (isObjectProperty(prop)) {
        if (isIdentifier(prop.value)) {
            value = prop.value.name;
            if (value === 'undefined') value = undefined;
        } else if (isStringLiteral(prop.value)) {
            value = prop.value.value;
        } else if (isNumericLiteral(prop.value)) {
            value = prop.value.value;
        } else if (isBinaryExpression(prop.value)) {
            value = getBinaryExpressionValue(prop.value);
        } else if (isBooleanLiteral(prop.value)) {
            value = prop.value.value;
        } else if (isNullLiteral(prop.value)) {
            value = null;
        } else if (isArrowFunctionExpression(prop.value)) {
            const { start, end } = prop.value;
            if (start !== null && end !== null) {
                value = code.substring(start, end);
                if (options?.formatCode) value = formatCodeForScript(value);
            }
        } else if (isFunctionExpression(prop.value)) {
            const { start, end } = prop.value;
            if (start !== null && end !== null) {
                value = code.substring(start, end);
                if (options?.formatCode) value = formatCodeForAnonymousFunction(value);
            }
        } else if (isObjectExpression(prop.value)) {
            const { start, end } = prop.value;
            if (start !== null && end !== null) {
                value = code.substring(start, end);
                if (options?.formatCode) value = formatCodeForObject(value);
            }
        } else if (isArrayExpression(prop.value)) {
            const { start, end } = prop.value;
            if (start !== null && end !== null) {
                value = code.substring(start, end);
                if (options?.formatCode) value = formatCodeForArray(value);
            }
        } else {
            const { start, end } = prop.value;
            if (start !== null && end !== null) {
                value = code.substring(start, end);
            }
        }
    }

    if (isObjectMethod(prop)) {
        const { start, end } = prop.body;
        if (start !== null && end !== null) {
            value = code.substring(start, end);
            if (options?.formatCode) value = formatCodeForScript(value);
            value = 'function () ' + value;
        }
    }

    return value;
}

/**
 * 递归构造 BinaryExpression Value
 */
export function getBinaryExpressionValue(value: BinaryExpression | Identifier, result?: Array<string>) {
    const ret = result || [];
    if (isIdentifier(value)) {
        ret.push(value.name);
    } else if (isBinaryExpression(value)) {
        // @ts-ignore
        getBinaryExpressionValue(value.left, ret);
        // @ts-ignore
        getBinaryExpressionValue(value.right, ret);
    }
    return ret;
}

/**
 * 获取函数名
 */
export function getMethodName(method: ObjectMethod) {
    let name = null;
    if (isObjectMethod(method)) {
        if (isIdentifier(method.key)) {
            name = method.key.name;
        }
    }
    return name;
}

/**
 * 获取函数调用名
 */
export function getMethodCallName(statement: ExpressionStatement) {
    let name = '';
    if (isCallExpression(statement.expression)) {
        if (isMemberExpression(statement.expression.callee)) {
            if (isIdentifier(statement.expression.callee.property)) {
                name = statement.expression.callee.property.name;
            }
        }
    }
    return name;
}
